#include "../SQLStr.h"
#include "SQLiteCommand.h"
#include "SQLiteDatabase.h"
#include "SQLiteCursor.h"
#include "../../common/CppMemPool.h"
#include "../../common/ObjPtrT.h"
#include "sqlite3.h"

using namespace cppflib::collections;
using namespace cppflib::util;


namespace cppflib
{

namespace database
{

namespace sqlite
{

PRIVATE sqlite3_stmt * __GetStmt(ptrany_t pStmt)
{
   return static_cast<sqlite3_stmt*>(pStmt);
}

PRIVATE sqlite3* __GetDbConn(ptrany_t pDbConn)
{
   return static_cast<sqlite3*>(pDbConn);
}

/**
 *  Convert a place holder name to the corresponding numeric index
 */
PRIVATE int __GetPlaceHolderIndex(sqlite3_stmt * pStmt, const SQLStr::PlaceHolder &placeHolder)
{
   const CFString & placeHolderName = placeHolder.GetName();
   ObjPtrT<char> name(placeHolderName.ToUtf8String());
   int index = sqlite3_bind_parameter_index(pStmt, name);
   return index;
}


SQLiteCommand::SQLiteCommand(void)
{
   this->pDB = NULL;
   this->pStmt = NULL;
}

/**
 *  This command requires a database connection
 */
SQLiteCommand::SQLiteCommand(SQLiteDatabase * pDB)
{
   this->pDB = pDB;
   cppmempool::Retain(this->pDB);
   this->pStmt = NULL;
}

SQLiteCommand::~SQLiteCommand(void)
{
   Close();
}

/**
 *  Clear allocated resources
 */
void SQLiteCommand::Reset()
{
   if (!query.IsEmpty()) {
      query = "";
   }

   if (this->pStmt) {
      sqlite3_finalize(__GetStmt(this->pStmt));
      this->pStmt = NULL;
   }
}

/**
 *  Release resources
 */
void SQLiteCommand::Close()
{
   if (this->pDB) {
      cppmempool::Release(this->pDB);
      this->pDB = NULL;
   }

   Reset();
}

/**
 *  Prepare a SQLite statement according to the query string
 */
bool SQLiteCommand::SetQueryString(const CFString &query)
{
   if (this->pStmt || !this->query.IsEmpty())
      Reset();

   this->query = query;

   sqlite3_stmt *pTempStmt = NULL;
   int ret = sqlite3_prepare16_v2(__GetDbConn(this->pDB->GetDbConn()), 
                                  static_cast<pcwstr_t>(this->query), -1, &pTempStmt, NULL);
   if (ret == SQLITE_OK && pTempStmt != NULL)
      this->pStmt = static_cast<ptrany_t>(pTempStmt);

   return (ret == SQLITE_OK);
}

/**
 *  To execute CREATE TABLE, INSERT, UPDATE, DELETE
 *
 *  @return number of rows changed
 */
bool SQLiteCommand::ExecNonQuery()
{
   if (pStmt) {
      bool isOK = SQLiteCursor::EvaluateSqlStmt(pStmt, true);
      // reset statement so that it can be re-executed later
      sqlite3_reset(__GetStmt(pStmt)); 
      // clear bindings, too, if any
      sqlite3_clear_bindings(__GetStmt(pStmt));

      return isOK;
   }
       
   return false;
}

/**
 *  To execute SELECT
 */
SQLiteCursor * SQLiteCommand::ExecQuery()
{
   if (pStmt) {
      SQLiteCursor * cursor = new SQLiteCursor(pStmt, pDB); // pass the ownership of pStmt to SQLiteCursor
      pStmt = NULL;
      Reset();
      return cursor;
   }

   return NULL;
}

/**
 *  Get the row ID just after a 'INSERT' is performed
 */
i64_t SQLiteCommand::GetLastInsertRowID()
{
   return pDB->GetLastInsertRowID();
}

/**
 *  Get number of changed row by a recent INSERT, UPDATE or DELETE statement
 */
int SQLiteCommand::GetRecentRowChanges()
{
   return sqlite3_changes(__GetDbConn(this->pDB->GetDbConn()));
}
    
bool SQLiteCommand::BindNull(const SQLStr::PlaceHolder &placeHolder)
{
   if (pStmt) {
      int index = __GetPlaceHolderIndex(__GetStmt(pStmt), placeHolder);
      if (index > 0)
         return (sqlite3_bind_null(__GetStmt(pStmt), index) == SQLITE_OK);
   }
   return false;
}

/**
 *  @note binBuf should *NOT* be released/modified before ExecNonQuery is called
 */
bool SQLiteCommand::BindBlob(const SQLStr::PlaceHolder &placeHolder, const collections::BinBuf &binBuf)
{
   if (pStmt) {
      int index = __GetPlaceHolderIndex(__GetStmt(pStmt), placeHolder);
      if (index > 0) 
         return (sqlite3_bind_blob(__GetStmt(pStmt), index, 
                                   binBuf.GetRawBuf(), static_cast<int>(binBuf.GetValidLength()), 
                                   SQLITE_STATIC) == SQLITE_OK);
   }
   return false;
}

/**
 *  Bind 'nrZeroByte' of bytes to the place holder.
 *
 *  The pre-occupied space can then be read/written by sqlite3_blob_read/sqlite3_blob_write
 */
bool SQLiteCommand::BindZeroBlob(const SQLStr::PlaceHolder &placeHolder, int nrZeroByte)
{
   if (pStmt) {
      int index = __GetPlaceHolderIndex(__GetStmt(pStmt), placeHolder);
      if (index > 0)
         return (sqlite3_bind_zeroblob(__GetStmt(pStmt), index, nrZeroByte) == SQLITE_OK);
   }
   return false;
}

bool SQLiteCommand::BindInt32(const SQLStr::PlaceHolder &placeHolder, int val)
{
   if (pStmt) {
      int index = __GetPlaceHolderIndex(__GetStmt(pStmt), placeHolder);
      if (index > 0)
         return (sqlite3_bind_int(__GetStmt(pStmt), index, val) == SQLITE_OK);
   }
   return false;
}

bool SQLiteCommand::BindInt64(const SQLStr::PlaceHolder &placeHolder, i64_t val)
{
   if (pStmt) {
      int index = __GetPlaceHolderIndex(__GetStmt(pStmt), placeHolder);
      if (index > 0)
         return (sqlite3_bind_int64(__GetStmt(pStmt), index, val) == SQLITE_OK);
   }
   return false;
}

bool SQLiteCommand::BindDouble(const SQLStr::PlaceHolder &placeHolder, double val)
{
   if (pStmt) {
      int index = __GetPlaceHolderIndex(__GetStmt(pStmt), placeHolder);
      if (index > 0)
         return (sqlite3_bind_double(__GetStmt(pStmt), index, val) == SQLITE_OK);
   }
   return false;
}

/**
 *  @note val should *NOT* be released/modified before ExecNonQuery is called
 */
bool SQLiteCommand::BindString(const SQLStr::PlaceHolder &placeHolder, const CFString &val)
{
   if (pStmt) {
      int index = __GetPlaceHolderIndex(__GetStmt(pStmt), placeHolder);
      if (index > 0) {
         
         return (sqlite3_bind_text16(__GetStmt(pStmt), index, 
                                     static_cast<pcwstr_t>(val), val.GetLength() * sizeof(wc_t),
                                     SQLITE_STATIC) == SQLITE_OK);
      }
   }
   return false;
}

/**
 *  Extract date part of DateTime only
 */
bool SQLiteCommand::BindDate(const SQLStr::PlaceHolder &placeHolder, const util::DateTime &val)
{
   if (pStmt) {
      CFString *str = new CFString();
      str->Format(_S("%04d-%02d-%02d"), val.Year(), val.Month(), val.Day());
      return BindString(placeHolder, *str);
   }
   return false;
}

/**
 *  Extract time part of DateTime only
 */
bool SQLiteCommand::BindTime(const SQLStr::PlaceHolder &placeHolder, const util::DateTime &val)
{
   if (pStmt) {
      CFString *str = new CFString();
      str->Format(_S("%02d:%02d:%02d"), val.Hour(), val.Min(), val.Sec());
      return BindString(placeHolder, *str);
   }
   return false;
}

/**
 *  Extract the whole DateTime
 */
bool SQLiteCommand::BindDateTime(const SQLStr::PlaceHolder &placeHolder, const util::DateTime &val)
{
   if (pStmt) {
      CFString *str = new CFString();
      str->Format(_S("%04d-%02d-%02d %02d:%02d:%02d"), val.Year(), val.Month(), val.Day(), 
                                                       val.Hour(), val.Min(), val.Sec());
      return BindString(placeHolder, *str);
   }
   return false;
}



} // end of namespace sqlite

} // end of namespace database

} // end of namespace cppflib
